package sqlTools.orm;

import java.lang.reflect.*;
import java.sql.*;
import sqlTools.*;


public class ORMField implements Comparable {
	
	
	Field fieldInOrmObject;
	String nameInTable;
	int position;
	Method pStmtSetter;
	Method rSetGetter;
	
	
	/**
		field : the Reflection object representing this field in 
			orm object
		colName : the name of this field as it appears in the db table.

	*/
	public ORMField (Field field, String colName) {
		this.fieldInOrmObject = field;
		this.nameInTable = colName;;
	}
	
	public ORMField (ORMObject obj, String fieldName, String colName) {
		Field field = null;
		try {
			field = obj.getClass().getField(fieldName);
		} catch (SecurityException e) {
			throw new RuntimeException("[ORMField.<init>] exception acceessing fields: "+fieldName+" : "+e.getMessage());
		} catch (NoSuchFieldException e) {
			throw new RuntimeException("[ORMField.<init>] no such field: "+fieldName+" : "+e.getMessage());
		}
		//this(field, colName);
	}


	public String getName() {
		return fieldInOrmObject.getName();	
	}

	public Object getValue (ORMObject obj) {
		try {
			boolean acc = fieldInOrmObject.isAccessible();
			fieldInOrmObject.setAccessible(true);
			Object ret = fieldInOrmObject.get(obj);
			fieldInOrmObject.setAccessible(acc);
			return ret;
		} catch (IllegalAccessException iae) {
			throw new RuntimeException("[ORMField.getValue] couldn't access field:"+getName()+" reason: "+iae.getMessage());	
		}
	}
	
	public String getNameInTable() {
		return nameInTable;	
	}

	public void setNameInTable(String name) {
		this.nameInTable = name;	
	}
	
	/**
		position : the position this field appears in in the insert and
			update preparedStatements generated by this objects
			orm class.
		
	*/
	public void setPosition (int position) {
		this.position = position;	
	}
	
	/**
		sqlType : the java.sql.Types type of this field in the db.
	*/
	public void setType (int sqlType) {
		pStmtSetter = TypesUtil.getSetMethod(sqlType);
		rSetGetter = TypesUtil.getGetMethod(sqlType);
		checkType(sqlType);

	}
	
	private void checkType (int sqlType) {
		if (!TypesUtil.getJavaType(sqlType).isAssignableFrom(fieldInOrmObject.getType())) {
			if ((sqlType == Types.DECIMAL || sqlType == Types.NUMERIC) && INT_CLASS.equals(fieldInOrmObject.getType())) {
				// Exception, allow to use int for DECIMAL (NUMERIC)
				// DatabaseMetaData allows checking the precision of
				// numeric types, so we COULD check during initialization, if
				// the numeric type would fit. For now, just issue a warning. (TODO)
				System.err.println(	"[Warning] Database type DECIMAL and NUMERIC should be mapped to "+
							" java.math.BigDecimal but are mapped to int in: "+fieldInOrmObject.getDeclaringClass().getName()+" - "+fieldInOrmObject.getName());
				setType (Types.INTEGER);
			} else {
				
				throw new RuntimeException (	"[ORMField.checkType] types not compatible: "+
								TypesUtil.getTypeName(sqlType)+ 
								" and "+fieldInOrmObject.getType().getName()+
								" of field: "+fieldInOrmObject.getName());
			}
		}
	}
	
	public void get (ResultSet rset, ORMObject obj) {
		Object [] parameter = {getNameInTable()};
		try {
			Object value =  rSetGetter.invoke(rset,parameter);
			boolean acc = fieldInOrmObject.isAccessible();
			fieldInOrmObject.setAccessible(true);
			fieldInOrmObject.set(obj, value);
			fieldInOrmObject.setAccessible(acc);
//			ORMObject.setField(obj, fieldInOrmObject, value);
		} catch (java.lang.reflect.InvocationTargetException ite) {
			ite.printStackTrace();
			throw new RuntimeException("[ORMField.get] (InvocationTargetException) couldn't access field: "+getName());	
		} catch (IllegalArgumentException iae) {
			iae.printStackTrace();
			throw new RuntimeException("[ORMField.get] (IllegalArgumentException) couldn't access field: "+getName());
		} catch (IllegalAccessException ilae) {
			ilae.printStackTrace();
			throw new RuntimeException("[ORMField.get] (IllegalAccessException) couldn't access field: "+getName());
		}
	}
	
	public void setFieldValue (ORMObject object, Object value) {
		try {
			this.fieldInOrmObject.set(object, value);	
		} catch (IllegalAccessException iae) {
			iae.printStackTrace();
			throw new RuntimeException("[ORMField.setFieldValue] can't set field: "+nameInTable+"with "+object);	
		}
	}
	
	public void set (PreparedStatement pStmt, Object value) throws SQLException {
		Object [] parameter = {new Integer(position), value};
		try {
			pStmtSetter.invoke(pStmt, parameter);
		} catch (IllegalAccessException iae) {
			throw new RuntimeException("[ORMField.set] (IllegalAccessException) couldn't access field:"+getName()+", method:"+pStmtSetter.getName()+" reason: "+iae.getMessage());	
		} catch (java.lang.reflect.InvocationTargetException ie){
			
			throw new RuntimeException("[ORMField.set] (InvocationTargetException) couldn't access field:"+getName()+", method:"+pStmtSetter.getName()+" reason: "+ie.getMessage());	
		}
	}

	public void set (PreparedStatement pStmt, Object value, int pos) throws SQLException {
		Object [] parameter = {new Integer(pos), value};
		try {
			pStmtSetter.invoke(pStmt, parameter);
		} catch (IllegalAccessException iae) {
			throw new RuntimeException("[ORMField.set] (IllegalAccessException) couldn't access field:"+getName()+", method:"+pStmtSetter.getName()+" reason: "+iae.getMessage());	
		} catch (java.lang.reflect.InvocationTargetException ie){
			
			throw new RuntimeException("[ORMField.set] (InvocationTargetException) couldn't access field:"+getName()+", method:"+pStmtSetter.getName()+" reason: "+ie.getMessage());	
		}
	}


	
	public void set (ORMObject orm, PreparedStatement pStmt) throws SQLException{
		set(pStmt, getValue(orm));	
	}
	public void set (ORMObject orm, PreparedStatement pStmt, int pos) throws SQLException{
		set(pStmt, getValue(orm), pos);	
	}

	public void set(PreparedStatement pStmt, double value) throws SQLException {
		if ((fieldInOrmObject.getType() != DOUBLE_CLASS) || (fieldInOrmObject.getType() != FLOAT_CLASS))
			throw new IllegalArgumentException ("[ORMField.set] double is wrong type for field: "+fieldInOrmObject.getName());
		pStmt.setDouble(position, value);
	}
	private static final Class DOUBLE_CLASS = new double[0].getClass().getComponentType();
	
	public void set(PreparedStatement pStmt, float value) throws SQLException {
		if ((fieldInOrmObject.getType() != DOUBLE_CLASS) || (fieldInOrmObject.getType() != FLOAT_CLASS))
			throw new IllegalArgumentException ("[ORMField.set] float is wrong type for field: "+fieldInOrmObject.getName());
		pStmt.setFloat(position, value);
			
	}
	private static final Class FLOAT_CLASS = new float[0].getClass().getComponentType();
	
	public void set(PreparedStatement pStmt, int value) throws SQLException {
		if (fieldInOrmObject.getType() != INT_CLASS)
			throw new IllegalArgumentException ("[ORMField.set] int is wrong type for field: "+fieldInOrmObject.getName());
		pStmt.setInt(position, value);
			
	}
	private static final Class INT_CLASS = new int[0].getClass().getComponentType();
	
	
	public void set(PreparedStatement pStmt, short value) throws SQLException {
		if (fieldInOrmObject.getType() != SHORT_CLASS)
			throw new IllegalArgumentException ("[ORMField.set] int is wrong type for field: "+fieldInOrmObject.getName());
		pStmt.setShort(position, value);
	}
	private static final Class SHORT_CLASS = new short[0].getClass().getComponentType();

	
	public void set(PreparedStatement pStmt, long value) throws SQLException {
		if (fieldInOrmObject.getType() != LONG_CLASS)
			throw new IllegalArgumentException ("[ORMField.set] long is wrong type for field: "+fieldInOrmObject.getName());
		pStmt.setLong(position, value);
	}
	private static final Class LONG_CLASS = new long[0].getClass().getComponentType();

	public void set (PreparedStatement pStmt, boolean value) throws SQLException {
		if (fieldInOrmObject.getType() != BOOLEAN_CLASS)
			throw new IllegalArgumentException ("[ORMField.set] boolean is wrong type for field: "+fieldInOrmObject.getName());
		pStmt.setBoolean(position, value);
	}
	private static final Class BOOLEAN_CLASS = new boolean[0].getClass().getComponentType();

	public void set (PreparedStatement pStmt, byte[] value) throws SQLException {
		if (fieldInOrmObject.getType() != BYTE_ARRAY_CLASS)
			throw new IllegalArgumentException ("[ORMField.set] byte[] is wrong type for field: "+fieldInOrmObject.getName());
		pStmt.setBytes(position, value);
	
	}
	private static final Class BYTE_ARRAY_CLASS = new byte[0].getClass();	

	public int compareTo (Object obj) {
		if (obj == null || (! (obj instanceof ORMField))) {
			throw new RuntimeException ("[ORMField.compareTo] can't compare Apples and Oranges");
		}		
		ORMField other = (ORMField)obj;
		if (other.position == this.position)
			return 0;
		if (other.position > this.position)
			return -1;
		return 1;
	}

	public String toString () {
		return "ORM:"+getName()+" DB:"+getNameInTable();
	}
	
}
